#!/usr/bin/python
# gcg Project
# GHDL Code Gen (gcg) - (http://code.google.com/p/gcg/)
#
# Rogerio A. Goncalves
# rogerio.rag@gmail.com
# http://rag.pro.br
#
# The new.py is called by make new target to create new entity.

# new.py creates the entity and its architecture.

import sys
import os
import re
import subprocess
from collections import OrderedDict
from operator import itemgetter

# ----------------------------------------------------------------------
# Create header files.
def create_header(tp, file_, desc ):
	# print str;
	newlines = []
	print(" Creating file:"+ file_.name)
	newlines.append("-- "+tp+" generated by script.\n")
	p = subprocess.Popen("date +%a,%d/%m/%Y-%H:%M:%S", stdout=subprocess.PIPE, shell=True)
	(output, err) = p.communicate()
	newlines.append("-- Date: " + output)
	p = subprocess.Popen("echo $USER", stdout=subprocess.PIPE, shell=True)
	(output, err) = p.communicate()
	newlines.append("-- Author: " + output)
	newlines.append("-- Comments: "+ desc + ".\n")
	newlines.append(" \n")

	# print(newlines)
	# for line in newlines:
	#	print line

	for line in newlines:
			file_.write(line)
	return;
# ----------------------------------------------------------------------
# Create Ports declaration.
def generate_ports_declaration(ports, prefix, inout):
	# >>> iports = {'b': 5, 'c': 1, 'd': 3, 'a': 1, 'e': 2}
	# >>> ports_ordered_by_size = OrderedDict(sorted(iports.iteritems(), key=lambda x: x[1]))
	# >>> print ports_ordered_by_size
  # OrderedDict([('a', 1), ('c', 1), ('e', 2), ('d', 3), ('b', 5)])

	str_result = ""
	ports_ordered_by_size = OrderedDict(sorted(ports.iteritems(), key=lambda x: x[1]))

	list_ports_size_one = "";
	# coloca as portas de tamanho 1 juntas. a, b, x: in std_logic.
	for name, size in ports_ordered_by_size.items():
			if size == 1:
					list_ports_size_one = list_ports_size_one + prefix + name + ", ";

	# retira o ultimo ',' que era gerado.
	list_ports_size_one = list_ports_size_one[:len(list_ports_size_one)-2]
	list_ports_size_one += ": " + inout + " std_logic;";

	# portas com tamanho maior que 1.
	for name, size in ports_ordered_by_size.items():
		if size != 1:
			str_result += prefix + name + ": " + inout + " std_logic_vector("+  str(size) +" downto 0); ";

	# Lista completa com portas std_logic e std_logic_vector.
	str_result = list_ports_size_one + " " + str_result;

	for key in ports_ordered_by_size:
		print 'portas ', key, '', ports[key]

	return str_result

# ----------------------------------------------------------------------
# Create Components Declaration.
def generate_components_declaration():
	str_result = "-- Declaration of Components.\n  component <<COMPONENT_NAME>>\n    port(<<ports_in>>: in <<type>>; <<ports_out>>: out <<type>>);\n  end component;\n"
	return str_result

# ----------------------------------------------------------------------
# Create Signals Declaration.
def generate_signals_declaration():
	str_result = "-- Signals declaration.\n  signal <<s_sinal_1>>, <<s_sinal_2>>, <<s_sinal_N>>: <<type>> <<length>>;\n"
	return str_result

# ----------------------------------------------------------------------
# Create Components Instances Declaration.
def generate_components_instances_declaration():
	str_result = "-- Commands.\n  -- Components instantiation and port mapping.\n  <<instance_name>>: <<COMPONENT_NAME>> port map(<<port1>>=><<portX>>, <<port2>>=><<portY>>);"
	return str_result

# ----------------------------------------------------------------------
# Create Test Signals Declaration.
def generate_test_signals_declaration(allports):
	str_result = "TODO:signal <<s_t_sinais>>: <<type>>;"
	return str_result;

# ----------------------------------------------------------------------
# Create Procedure Print Messages.
def generate_procedure_print_messages(iports, oports):
	# pi: input value parameter
	# po: output value parameter
	# pe: expected value parameter
	# procedure print_message( pi_s_t_x, pi_s_t_y: std_logic;  po_s_t_z: std_logic;  pe_z: std_logic) is
	str_result =  "-- procedure print messages.\n" \
								"  procedure print_message(<<params_in>>: <<type>>; <<params_out>>: <<type>>; <<params_exp>>: <<type>>) is\n" \
								"  variable line_out: line;\n" \
								"  begin\n" \
								"    write(line_out, string'(\"   At time \"));\n" \
								"    write(line_out, now);\n" \
								"    write(line_out, string'(\", inputs [\"));" \
								"    <<print_in_var_values>>\n" \
								"    write(line_out, string'(\"]\"));\n" \
								"    write(line_out, string'(\", outputs [\"));" \
								"    <<print_out_var_values>>\n" \
								"    write(line_out, string'(\"]\"));" \
								"    if <<compare_gen_exp>> then\n" \
								"       write(line_out, string'(\" [OK]\"));\n" \
								"    else\n" \
								"       write(line_out, string'(\" [Error]\"));\n" \
								"    end if;\n" \
								"    writeline(output, line_out);\n" \
								"  end procedure print_message;"

	return str_result

# ----------------------------------------------------------------------
# Create Port Map Testebench.
def generate_port_map_entity_tb():
	str_result =  "TODO:<<port_map_entity_tb>>"
	return str_result

# ----------------------------------------------------------------------
# Create Record Pattern Type Declaration.
def generate_record_pattern_type_declaration(iports, oports):
	# -- inputs.
	# vi_x, vi_y: std_logic;
	# -- outputs.
	# vo_z: std_logic;

	# print 'pattern: ', generate_ports_declaration(iports, "vi_")

	#str_result = "type pattern_type is record\n" \
	#						 "          -- inputs.\n" \
	#						 "          <<record_in_vars>>: <<type>>;\n" \
	#						 "          -- outputs.\n" \
	#						 "          <<record_out_vars>>: <<type>>;\n" \
	#						 "        end record;"

	str_result = "type pattern_type is record\n" \
							 "          -- inputs.\n" \
							 "          <<record_in_vars>>\n" \
							 "          -- outputs.\n" \
							 "          <<record_out_vars>>\n" \
							 "        end record;"

	str_result = str_result.replace('<<record_in_vars>>', generate_ports_declaration(iports, 'vi_', ''))
	str_result = str_result.replace('<<record_out_vars>>', generate_ports_declaration(oports, 'vo_',''))

	return str_result

# ----------------------------------------------------------------------
# Create Testcases Declaration.
def generate_testcases_declaration(iports, oports):
	# >>> for i in range(0,16):
	#...     print '(' + '{0:04b}'.format(i) +'),'
	#...
	#(0000),
	#(0001),
	#(0010),
	#(0011),
	#(0100),
	#(0101),
	#(0110),
	#(0111),
	#(1000),
	#(1001),
	#(1010),
	#(1011),
	#(1100),
	#(1101),
	#(1110),
	#(1111),

	str_result = "(test cases with <<case_test_model>> columns, '0'...),\n" \
								"          (...)"
	return str_result

# ----------------------------------------------------------------------
# Create Input Signals Injection Declaration.
def generate_input_signals_injection_declaration():
	# -- Injects the inputs.
	#		s_t_x <= patterns(i).vi_x;
	#		s_t_y <= patterns(i).vi_y;
	str_result = "TODO:<<input_signals_injection>>"
	return str_result

# ----------------------------------------------------------------------
# Create Print Message Call Declaration.
def generate_print_message_call_declaration():
	str_result = "print_message(<<params_in_print_call>>, <<params_out_print_call>>, <<params_exp_print_call>>);"

	return str_result

# ----------------------------------------------------------------------
# Create Assert Variables Declaration.
def generate_assert_vars_declaration():
	str_result = "TODO:<<asserts_vars>>"

	return str_result
# ----------------------------------------------------------------------

# ----------------------------------------------------------------------
# Main code.
# ----------------------------------------------------------------------
# Total args.
total = len(sys.argv)

# arguments list.
project, arch, pin, pout = sys.argv[1:]

# print project
# print arch

# Dictionaries to input and output ports.
iports = {}
oports = {}

# Extract input ports.
pins = pin.split(',')
for pin in pins:
	# print pin
	p = re.search('(?P<var>\w+)?(\[(?P<nbits>\d+)?\])?', pin)
	var = p.group('var')
	if p.group('nbits'):
		n = int(p.group('nbits'))
	else:
		n = 1
	# print "teste: ", var, n
	iports[var] = n
print 'input ports: ',iports

# Extract output ports.
pouts = pout.split(',')
for pout in pouts:
	# print pout
	p = re.search('(?P<var>\w+)?(\[(?P<nbits>\d+)?\])?', pout)
	var = p.group('var')
	if p.group('nbits'):
		n = int(p.group('nbits'))
	else:
		n = 1
	# print "teste: ", var, n
	oports[var] = n
print 'output ports: ', oports

# Open new entity file.
f_entity = open("./src/" + project + ".vhd", 'w')
# Open new testbench file.
f_tb = open("./testbench/" + project + "_tb.vhd", 'w')

# Create header of entity file.
create_header("Project", f_entity, "Entity Description: " + project)

# Create the entity file.
# Prepare input ports declaration.
str_ports_in = generate_ports_declaration(iports, "", "in")
# Prepare output ports declaration.
str_ports_out = generate_ports_declaration(oports, "", "out")
# retira o ultimo ';'' que era gerado.
str_ports_out = str_ports_out[:len(str_ports_out)-2]

# Prepare components declaration.
if arch.lower() in ['estrutural', 'structural']:
	components_decl = generate_components_declaration()
else:
	components_decl = "";

# Prepare signals declaration.
signals_decl = generate_signals_declaration()

# Prepare components instances declaration.
components_instances_decl = generate_components_instances_declaration();

# Reading entity template.
with open('./templates/entity.vhd','r') as f:
    newlines = []
		# Read template lines and replace tags.
    for line in f.readlines():
				linep = line.replace('<<ENTITY_NAME>>', project)
				linep = linep.replace('<<ARCH_TYPE>>', arch)
				linep = linep.replace('<<IN_P>>', str_ports_in)
				linep = linep.replace('<<OUT_P>>', str_ports_out)
				linep = linep.replace('<<DECL_COMPONENTS>>', components_decl)
				linep = linep.replace('<<DECL_SIGNALS>>', signals_decl)
				linep = linep.replace('<<DECL_COMP_INSTANCES>>', components_instances_decl)

				# Append modified line on newlines.
				newlines.append(linep)

# Write modified lines on new entity file.
for line in newlines:
	f_entity.write(line)

# Create header of testbench file.
create_header("Testbench", f_tb, "Test of "+project+" entity")

allports = "a,b,c"

# Prepare test signals declaration.
test_signals_decl = generate_test_signals_declaration(allports)

# Reading entity template.
with open('./templates/entity_tb.vhd','r') as f:
    newlines = []
		# Read template lines and replace tags.
    for line in f.readlines():
				linep = line.replace('<<ENTITY_NAME>>', project)
				linep = linep.replace('<<ARCH_TYPE>>', arch)
				linep = linep.replace('<<IN_P>>', str_ports_in)
				linep = linep.replace('<<OUT_P>>', str_ports_out)
				linep = linep.replace('<<DECL_TEST_SIGNALS>>', test_signals_decl)
				linep = linep.replace('<<PROC_PRINT_MSGS>>', generate_procedure_print_messages(iports, oports))
				linep = linep.replace('<<port_map_entity_tb>>', generate_port_map_entity_tb())
				linep = linep.replace('<<RECORD_PATTERN_TYPE_DECLARATION>>', generate_record_pattern_type_declaration(iports, oports))
				linep = linep.replace('<<TESTCASES_DECLARATION>>', generate_testcases_declaration(iports, oports))
				linep = linep.replace('<<input_signals_injection>>', generate_input_signals_injection_declaration())
				linep = linep.replace('<<PRINT_MESSAGE_CALL>>', generate_print_message_call_declaration())
				linep = linep.replace('<<asserts_vars>>', generate_assert_vars_declaration())

				# Append modified line on newlines.
				newlines.append(linep)

# Write modified lines on new testbench file.
for line in newlines:
	f_tb.write(line)
